Skip to content

fix: restrict client external navigation to http(s) and protocol-relative URLs#4

Open
JaredStowell wants to merge 1 commit intomainfrom
codex/propose-fix-for-javascript-url-vulnerability
Open

fix: restrict client external navigation to http(s) and protocol-relative URLs#4
JaredStowell wants to merge 1 commit intomainfrom
codex/propose-fix-for-javascript-url-vulnerability

Conversation

@JaredStowell
Copy link
Owner

Motivation

  • Client navigation helpers were treating any RFC 3986 scheme as "external", which caused javascript:/data: URLs to be delegated to window.location, creating a DOM XSS sink when untrusted input was used.
  • The intent is to preserve safe behavior for router/navigation APIs by only delegating true web navigations to the browser.

Description

  • Narrowed isExternalUrl in the Pages Router shim (packages/vinext/src/shims/router.ts) to return true only for http://, https://, or protocol-relative (//) URLs.
  • Made the same change to the App Router/navigation shim (packages/vinext/src/shims/navigation.ts) so both client navigation paths are consistent.
  • Added regression assertions to tests/link.test.ts and tests/shims.test.ts that explicitly expect javascript: and data: schemes to be treated as non-external.
  • Changes are minimal and localized to client-side external URL detection and test expectations.

Testing

  • Ran the targeted isExternalUrl unit tests with pnpm vitest run tests/link.test.ts -t "isExternalUrl" and they passed.
  • Ran the related Pages Router router-helpers tests with pnpm vitest run tests/shims.test.ts -t "Pages Router router helpers" and they passed.
  • A broader run of pnpm vitest run tests/link.test.ts in this environment surfaced unrelated React hook/runtime test failures; the failures are unrelated to this change and the targeted assertions for isExternalUrl succeed.

Codex Task

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8d1a4042e9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

*/
function isExternalUrl(href: string): boolean {
return /^[a-z][a-z0-9+.-]*:/i.test(href) || href.startsWith("//");
return /^https?:\/\//i.test(href) || href.startsWith("//");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat non-HTTP absolute schemes as external in App Router

Restricting isExternalUrl to only http(s) and // makes values like mailto:, tel:, and sms: fall into the internal navigation path, so useRouter().push()/replace() reaches history.pushState with a non-HTTP absolute URL and can throw SecurityError in browsers instead of delegating to native navigation. This is a regression from the previous behavior and breaks legitimate external protocol links; only dangerous schemes should be blocked.

Useful? React with 👍 / 👎.

/** Check if a URL is external (http/https, or protocol-relative) */
export function isExternalUrl(url: string): boolean {
return /^[a-z][a-z0-9+.-]*:/i.test(url) || url.startsWith("//");
return /^https?:\/\//i.test(url) || url.startsWith("//");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep non-HTTP schemes external in Pages Router helper

The same narrowing in the Pages Router isExternalUrl means next/router push/replace now treats mailto:/tel: URLs as internal, then executes window.history.pushState/replaceState on those values and can throw at runtime (or misroute when a basePath is set). This breaks existing external-protocol navigation semantics; the helper should still classify non-dangerous absolute schemes as external.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant